Repository navigation
initial implementation of the groth16 verifier - #4
Conversation
… arkworks, registry and malformed-input tests
vadorovsky
left a comment
There was a problem hiding this comment.
Still not fully done with reviewing, but I have a couple of comments for now.
There was a problem hiding this comment.
Could we get rid of this issue by updating to ark-relations 0.6 (and in general, by updating all the arkworks crates)?
There was a problem hiding this comment.
Yeah we used ark 0.5 for the agave and cryptography repo due to potential breaking changes, but I don't think there is a reason to stick with the old version in this repo.
|
|
||
| ## Build and test | ||
|
|
||
| Host toolchain is pinned to stable `1.93.1` in `rust-toolchain.toml`. SBF |
There was a problem hiding this comment.
Not sure if we should mention Rust versions in README. If you keep it, every time we bump the version, we end up with an additional chore of having to update README.
| let mut data = Vec::with_capacity(3); | ||
| data.push(Tag::InitializeStaging as u8); | ||
| data.extend_from_slice(&num_public_inputs.to_le_bytes()); |
There was a problem hiding this comment.
We could avoid a heap allocation and use an array instead, especially since we are dealing with 3 bytes here. 😄
| let mut data = Vec::with_capacity(3); | |
| data.push(Tag::InitializeStaging as u8); | |
| data.extend_from_slice(&num_public_inputs.to_le_bytes()); | |
| let [lo, hi] = num_public_inputs.to_le_bytes(); | |
| let data = [Tag::InitializeStaging as u8, lo, hi]; |
| return Err(Groth16ProgramError::IdentityKeyElement.into()); | ||
| } | ||
|
|
||
| let mut input = [0u8; 3 * PAIRING_ELEMENT_SIZE]; |
There was a problem hiding this comment.
Instead of initializing the array with zeros, we could either:
- Use https://crates.io/crates/arrayvec - my preference, since it provides safe abstraction over
MaybeUninit. - Use
MaybeUninitand deal withunsafeoperations ourselves.
I can come up with more specific proposal tomorrow.
| acc = g1_add(&acc, ic)?; | ||
| } else { | ||
| let term = g1_mul(ic, s)?; | ||
| acc = g1_add(&acc, &term)?; |
There was a problem hiding this comment.
Let me know if I am misunderstanding this, but we can skip if scalar is zero, right? Maybe we should do something like
let ic = vk.ic(i + 1);
if scalar::is_zero(ic) {
continue;
}
if scalar::is_one(s) {
acc = g1_add(&acc, ic)?;
} else {
let term = g1_mul(ic, s)?;
acc = g1_add(&acc, &term)?;
}
There was a problem hiding this comment.
Sorry I may have misunderstood the point but zero scalars are already skipped in line 67. Is that what you meant? or did you want to change the order of doing zero scalar check and ic increment?
There was a problem hiding this comment.
Yeah sorry, my wording might have been unclear. I meant checking whether the IC point is the identity (scalar::is_zero(ic)). Even when s is nonzero, multiplying an identity point contributes nothing, so we can skip that term. The existing is_zero(s) check handles zero scalars. Am I misunderstanding the logic here?
There was a problem hiding this comment.
oh thanks! I misread the scalars. fixed in 11b2ae7
There was a problem hiding this comment.
I think the PR looks fine to me as an initial version. The high-level design makes sense to me. Obviously, there are places that can be optimized, but these can be done in follow-up PRs iteratively.
I'll give an approval when any of the outstanding comments above are all addressed.
|
If you don't mind, I would like to have one more day for reviewing this. Although it's very likely that I will approve. (If there are any deadlines / you're on rush, I can do a post-merge review.) |
no hurries at all. take your time. thanks for the review! |
vadorovsky
left a comment
There was a problem hiding this comment.
Feel free to merge. ![]()
Just one non-blocking question that can be addressed later.
|
|
||
| #[inline] | ||
| pub fn a(&self) -> &'a [u8; G1_SIZE] { | ||
| fixed(self.bytes, PROOF_A_OFFSET) |
There was a problem hiding this comment.
The current design choice of this Proof struct is that we keep everything in one contiguous array, and then split it into a(), b(), c() etc. on demand. All these methods use fixed() to split the contiguous array into chunks and handle the size-related error.
How often do we expect to use the "chunking" methods a(), b(), c(), and how often do we expect to use as_bytes()? I feel like we are going to use the "chunking" methods way more often. If that's true, then perhaps we should consider keeping each component as a separate field, and then use wincode zero-copy serialization for as_bytes().
There was a problem hiding this comment.
On-chain, Verify calls only a_b() and c(), once each per proof, and never as_bytes(); A ‖ B is kept contiguous so pairing slot 0 is a single copy.
I feel like we are already zero-copying and perhaps we don't want to add wincode (an additional dependency)?
#2 got merged/closed because of a forced push. reopen this PR